/* Copyright (c) 2013-2015,2018 The Linux Foundation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 *       copyright notice, this list of conditions and the following
 *       disclaimer in the documentation and/or other materials provided
 *       with the distribution.
 *     * Neither the name of The Linux Foundation nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <string.h>
#include <stdlib.h>
#include <debug.h>
#include <reg.h>
#include <mmc_sdhci.h>
#include <sdhci.h>
#include <sdhci_msm.h>
#include <partition_parser.h>
#include <platform/iomap.h>
#include <platform/timer.h>
#include <platform.h>

extern void clock_init_mmc(uint32_t);
extern void clock_config_mmc(uint32_t, uint32_t);

/* data access time unit in ns */
static const uint32_t taac_unit[] =
{
	1, 10, 100, 1000, 10000, 100000, 1000000, 10000000
};

/* data access time value x 10 */
static const uint32_t taac_value[] =
{
	0, 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80
};

/* data transfer rate in kbit/s */
static const uint32_t xfer_rate_unit[] =
{
	100, 1000, 10000, 100000, 0, 0, 0, 0
};

/* data transfer rate value x 10*/
static const uint32_t xfer_rate_value[] =
{
	0, 10, 12, 13, 15, 20, 26, 30, 35, 40, 45, 52, 55, 60, 70, 80
};

/*
 * Function: mmc decode and save csd
 * Arg     : Card structure & raw csd
 * Return  : 0 on Success, 1 on Failure
 * Flow    : Decodes CSD response received from the card.
 *           Note that we have defined only few of the CSD elements
 *           in csd structure. We'll only decode those values.
 */
static uint32_t mmc_decode_and_save_csd(struct mmc_card *card)
{
	uint32_t mmc_sizeof = 0;
	uint32_t mmc_unit = 0;
	uint32_t mmc_value = 0;
	uint32_t mmc_temp = 0;
	uint32_t *raw_csd = card->raw_csd;

	struct mmc_csd mmc_csd;

	mmc_sizeof = sizeof(uint32_t) * 8;

	mmc_csd.cmmc_structure = UNPACK_BITS(raw_csd, 126, 2, mmc_sizeof);

	if (MMC_CARD_SD(card)) {
		/* Parse CSD according to SD card spec. */

		/* CSD register is little bit differnet for CSD version 2.0 High
		 * Capacity and CSD version 1.0/2.0 Standard memory cards.
		 * In Version 2.0 some of the fields have fixed values and it's
		 * not necessary for host to refer these fields in CSD sent by
		 * card
		 */

		if (mmc_csd.cmmc_structure == 1) {
			/* CSD Version 2.0 */
			mmc_csd.card_cmd_class = UNPACK_BITS(raw_csd, 84, 12, mmc_sizeof);
			/* Fixed value is 9 = 2^9 = 512 */
			mmc_csd.write_blk_len = 512;
			/* Fixed value is 9 = 512 */
			mmc_csd.read_blk_len = 512;
			/* Fixed value: 010b */
			mmc_csd.r2w_factor = 0x2;
			/* Not there in version 2.0 */
			mmc_csd.c_size_mult = 0;
			mmc_csd.c_size  = UNPACK_BITS(raw_csd, 48, 22, mmc_sizeof);
			mmc_csd.nsac_clk_cycle = UNPACK_BITS(raw_csd, 104, 8, mmc_sizeof)
									 * 100;

			mmc_unit = UNPACK_BITS(raw_csd, 112, 3, mmc_sizeof);
			mmc_value = UNPACK_BITS(raw_csd, 115, 4, mmc_sizeof);
			mmc_csd.taac_ns = (taac_value[mmc_value] * taac_unit[mmc_unit])
							   / 10;

			mmc_csd.erase_blk_len = 1;
			mmc_csd.read_blk_misalign = 0;
			mmc_csd.write_blk_misalign = 0;
			mmc_csd.read_blk_partial = 0;
			mmc_csd.write_blk_partial = 0;

			mmc_unit = UNPACK_BITS(raw_csd, 96, 3, mmc_sizeof);
			mmc_value = UNPACK_BITS(raw_csd, 99, 4, mmc_sizeof);
			mmc_csd.tran_speed = (xfer_rate_value[mmc_value] *
								 xfer_rate_unit[mmc_unit]) / 10;

			mmc_csd.wp_grp_size = 0x0;
			mmc_csd.wp_grp_enable = 0x0;
			mmc_csd.perm_wp = UNPACK_BITS(raw_csd, 13, 1, mmc_sizeof);
			mmc_csd.temp_wp = UNPACK_BITS(raw_csd, 12, 1, mmc_sizeof);

			/* Calculate the card capcity */
			card->capacity = (unsigned long long) (1 + mmc_csd.c_size) * 512 * 1024;
		} else {
			/* CSD Version 1.0 */
			mmc_csd.card_cmd_class = UNPACK_BITS(raw_csd, 84, 12, mmc_sizeof);

			mmc_temp = UNPACK_BITS(raw_csd, 22, 4, mmc_sizeof);
			mmc_csd.write_blk_len = (mmc_temp > 8 && mmc_temp < 12) ?
									(1 << mmc_temp) : 512;

			mmc_temp = UNPACK_BITS(raw_csd, 80, 4, mmc_sizeof);
			mmc_csd.read_blk_len = (mmc_temp > 8 && mmc_temp < 12) ?
								   (1 << mmc_temp) : 512;

			mmc_unit = UNPACK_BITS(raw_csd, 112, 3, mmc_sizeof);
			mmc_value = UNPACK_BITS(raw_csd, 115, 4, mmc_sizeof);
			mmc_csd.taac_ns = (taac_value[mmc_value] * taac_unit[mmc_unit])
							  / 10;

			mmc_unit = UNPACK_BITS(raw_csd, 96, 3, mmc_sizeof);
			mmc_value = UNPACK_BITS(raw_csd, 99, 4, mmc_sizeof);
			mmc_csd.tran_speed = (xfer_rate_value[mmc_value] *
								 xfer_rate_unit[mmc_unit]) / 10;

			mmc_csd.nsac_clk_cycle = UNPACK_BITS(raw_csd, 104, 8, mmc_sizeof)
									 * 100;

			mmc_csd.r2w_factor = UNPACK_BITS(raw_csd, 26, 3, mmc_sizeof);
			mmc_csd.sector_size = UNPACK_BITS(raw_csd, 39, 7, mmc_sizeof) + 1;

			mmc_csd.erase_blk_len = UNPACK_BITS(raw_csd, 46, 1, mmc_sizeof);
			mmc_csd.read_blk_misalign = UNPACK_BITS(raw_csd, 77, 1, mmc_sizeof);
			mmc_csd.write_blk_misalign = UNPACK_BITS(raw_csd, 78, 1, mmc_sizeof);
			mmc_csd.read_blk_partial = UNPACK_BITS(raw_csd, 79, 1, mmc_sizeof);
			mmc_csd.write_blk_partial = UNPACK_BITS(raw_csd, 21, 1, mmc_sizeof);

			mmc_csd.c_size_mult = UNPACK_BITS(raw_csd, 47, 3, mmc_sizeof);
			mmc_csd.c_size = UNPACK_BITS(raw_csd, 62, 12, mmc_sizeof);
			mmc_csd.wp_grp_size = UNPACK_BITS(raw_csd, 32, 7, mmc_sizeof);
			mmc_csd.wp_grp_enable = UNPACK_BITS(raw_csd, 31, 1, mmc_sizeof);
			mmc_csd.perm_wp = UNPACK_BITS(raw_csd, 13, 1, mmc_sizeof);
			mmc_csd.temp_wp = UNPACK_BITS(raw_csd, 12, 1, mmc_sizeof);

			/* Calculate the card capacity */
			mmc_temp = (1 << (mmc_csd.c_size_mult + 2)) * (mmc_csd.c_size + 1);
			card->capacity = (unsigned long long)mmc_temp * mmc_csd.read_blk_len;
		}
	} else {
		/* Parse CSD according to MMC card spec. */
		mmc_csd.spec_vers = UNPACK_BITS(raw_csd, 122, 4, mmc_sizeof);
		mmc_csd.card_cmd_class = UNPACK_BITS(raw_csd, 84, 12, mmc_sizeof);
		mmc_csd.write_blk_len = 1 << UNPACK_BITS(raw_csd, 22, 4, mmc_sizeof);
		mmc_csd.read_blk_len =  1 << UNPACK_BITS(raw_csd, 80, 4, mmc_sizeof);
		mmc_csd.r2w_factor = UNPACK_BITS(raw_csd, 26, 3, mmc_sizeof);
		mmc_csd.c_size_mult = UNPACK_BITS(raw_csd, 47, 3, mmc_sizeof);
		mmc_csd.c_size = UNPACK_BITS(raw_csd, 62, 12, mmc_sizeof);
		mmc_csd.nsac_clk_cycle = UNPACK_BITS(raw_csd, 104, 8, mmc_sizeof) * 100;

		mmc_unit = UNPACK_BITS(raw_csd, 112, 3, mmc_sizeof);
		mmc_value = UNPACK_BITS(raw_csd, 115, 4, mmc_sizeof);
		mmc_csd.taac_ns = (taac_value[mmc_value] * taac_unit[mmc_unit]) / 10;

		mmc_csd.read_blk_misalign = UNPACK_BITS(raw_csd, 77, 1, mmc_sizeof);
		mmc_csd.write_blk_misalign = UNPACK_BITS(raw_csd, 78, 1, mmc_sizeof);
		mmc_csd.read_blk_partial = UNPACK_BITS(raw_csd, 79, 1, mmc_sizeof);
		mmc_csd.write_blk_partial = UNPACK_BITS(raw_csd, 21, 1, mmc_sizeof);

		/* Ignore -- no use of this value. */
		mmc_csd.tran_speed = 0x00;

		mmc_csd.erase_grp_size = UNPACK_BITS(raw_csd, 42, 5, mmc_sizeof);
		mmc_csd.erase_grp_mult = UNPACK_BITS(raw_csd, 37, 5, mmc_sizeof);
		mmc_csd.wp_grp_size = UNPACK_BITS(raw_csd, 32, 5, mmc_sizeof);
		mmc_csd.wp_grp_enable = UNPACK_BITS(raw_csd, 31, 1, mmc_sizeof);
		mmc_csd.perm_wp = UNPACK_BITS(raw_csd, 13, 1, mmc_sizeof);
		mmc_csd.temp_wp = UNPACK_BITS(raw_csd, 12, 1, mmc_sizeof);

		/* Calculate the card capcity */
		if (mmc_csd.c_size != 0xFFF) {
			/* For cards less than or equal to 2GB */
			mmc_temp = (1 << (mmc_csd.c_size_mult + 2)) * (mmc_csd.c_size + 1);
			card->capacity = (unsigned long long) mmc_temp * mmc_csd.read_blk_len;
		} else {
			/* For cards greater than 2GB, Ext CSD register's SEC_COUNT
			 * is used to calculate the size.
			 */
			uint64_t sec_count;

			sec_count = (card->ext_csd[MMC_SEC_COUNT4] << MMC_SEC_COUNT4_SHIFT)
						| (card->ext_csd[MMC_SEC_COUNT3] << MMC_SEC_COUNT3_SHIFT)
						| (card->ext_csd[MMC_SEC_COUNT2] << MMC_SEC_COUNT2_SHIFT)
						| card->ext_csd[MMC_SEC_COUNT1];
			card->capacity = sec_count * MMC_BLK_SZ;
		}
	}

	/* save the information in card structure */
	memcpy((struct mmc_csd *)&card->csd,(struct mmc_csd *)&mmc_csd,
			sizeof(struct mmc_csd));

	if (MMC_CARD_MMC(card)) {

		/* Calculate the wp grp size */
		if (card->ext_csd[MMC_ERASE_GRP_DEF])
			card->wp_grp_size = MMC_HC_ERASE_MULT * card->ext_csd[MMC_HC_ERASE_GRP_SIZE] / MMC_BLK_SZ;
		else
			card->wp_grp_size = (card->csd.wp_grp_size + 1) * (card->csd.erase_grp_size + 1) \
						  * (card->csd.erase_grp_mult + 1);

		card->rpmb_size = RPMB_PART_MIN_SIZE * card->ext_csd[RPMB_SIZE_MULT];
		card->rel_wr_count = card->ext_csd[REL_WR_SEC_C];
	}
	else {
		card->wp_grp_size = (card->csd.wp_grp_size + 1) * (card->csd.erase_grp_size + 1) \
						* (card->csd.erase_grp_mult + 1);
	}

	dprintf(SPEW, "Decoded CSD fields:\n");
	dprintf(SPEW, "cmmc_structure: %u\n", mmc_csd.cmmc_structure);
	dprintf(SPEW, "card_cmd_class: %x\n", mmc_csd.card_cmd_class);
	dprintf(SPEW, "write_blk_len: %u\n", mmc_csd.write_blk_len);
	dprintf(SPEW, "read_blk_len: %u\n", mmc_csd.read_blk_len);
	dprintf(SPEW, "r2w_factor: %u\n", mmc_csd.r2w_factor);
	dprintf(SPEW, "sector_size: %u\n", mmc_csd.sector_size);
	dprintf(SPEW, "c_size_mult:%u\n", mmc_csd.c_size_mult);
	dprintf(SPEW, "c_size: %u\n", mmc_csd.c_size);
	dprintf(SPEW, "nsac_clk_cycle: %u\n", mmc_csd.nsac_clk_cycle);
	dprintf(SPEW, "taac_ns: %u\n", mmc_csd.taac_ns);
	dprintf(SPEW, "tran_speed: %u kbps\n", mmc_csd.tran_speed);
	dprintf(SPEW, "erase_blk_len: %u\n", mmc_csd.erase_blk_len);
	dprintf(SPEW, "read_blk_misalign: %u\n", mmc_csd.read_blk_misalign);
	dprintf(SPEW, "write_blk_misalign: %u\n", mmc_csd.write_blk_misalign);
	dprintf(SPEW, "read_blk_partial: %u\n", mmc_csd.read_blk_partial);
	dprintf(SPEW, "write_blk_partial: %u\n", mmc_csd.write_blk_partial);
	dprintf(SPEW, "wp_grp_size: %u\n", card->wp_grp_size);
	dprintf(SPEW, "Card Capacity: %llu Bytes\n", card->capacity);

	return 0;
}

/*
 * Function: mmc decode & save cid
 * Arg     : card structure & raw cid
 * Return  : 0 on Success, 1 on Failure
 * Flow    : Decode CID sent by the card.
 */
static uint32_t mmc_decode_and_save_cid(struct mmc_card *card,
										uint32_t *raw_cid)
{
	struct mmc_cid mmc_cid;
	uint32_t mmc_sizeof = 0;
	int i = 0;

	if (!raw_cid) {
		return 1;
	}

	mmc_sizeof = sizeof(uint32_t) * 8;

	if (MMC_CARD_SD(card)) {
		mmc_cid.mid = UNPACK_BITS(raw_cid, 120, 8, mmc_sizeof);
		mmc_cid.oid = UNPACK_BITS(raw_cid, 104, 16, mmc_sizeof);

		for (i = 0; i < 5; i++) {
			mmc_cid.pnm[i] = (uint8_t)UNPACK_BITS(raw_cid,
								    (104 - 8 * (i + 1)),
								    8,
								    mmc_sizeof);
		}
		mmc_cid.pnm[5] = 0;
		mmc_cid.pnm[6] = 0;

		mmc_cid.prv = UNPACK_BITS(raw_cid, 56, 8, mmc_sizeof);
		mmc_cid.psn = UNPACK_BITS(raw_cid, 24, 32, mmc_sizeof);
		mmc_cid.month = UNPACK_BITS(raw_cid, 8, 4, mmc_sizeof);
		mmc_cid.year = UNPACK_BITS(raw_cid, 12, 8, mmc_sizeof);
		mmc_cid.year += 2000;
	} else {
		mmc_cid.mid = UNPACK_BITS(raw_cid, 120, 8, mmc_sizeof);
		mmc_cid.oid = UNPACK_BITS(raw_cid, 104, 16, mmc_sizeof);

		for (i = 0; i < 6; i++) {
			mmc_cid.pnm[i] = (uint8_t)UNPACK_BITS(raw_cid, (104 - 8 * (i + 1)),
												  8, mmc_sizeof);
		}
		mmc_cid.pnm[6] = 0;

		mmc_cid.prv = UNPACK_BITS(raw_cid, 48, 8, mmc_sizeof);
		mmc_cid.psn = UNPACK_BITS(raw_cid, 16, 32, mmc_sizeof);
		mmc_cid.month = UNPACK_BITS(raw_cid, 8, 4, mmc_sizeof);
		mmc_cid.year = UNPACK_BITS(raw_cid, 12, 4, mmc_sizeof);
		mmc_cid.year += 1997;
	}

	/* save it in card database */
	memcpy((struct mmc_cid *)&card->cid,
	       (struct mmc_cid *)&mmc_cid, sizeof(struct mmc_cid));

	dprintf(SPEW, "Decoded CID fields:\n");
	dprintf(SPEW, "Manufacturer ID: %x\n", mmc_cid.mid);
	dprintf(SPEW, "OEM ID: 0x%x\n", mmc_cid.oid);
	dprintf(SPEW, "Product Name: %s\n", mmc_cid.pnm);
	dprintf(SPEW, "Product revision: %d.%d\n", (mmc_cid.prv >> 4),
											   (mmc_cid.prv & 0xF));
	dprintf(SPEW, "Product serial number: %X\n", mmc_cid.psn);
	dprintf(SPEW, "Manufacturing date: %d %d\n", mmc_cid.month, mmc_cid.year);

	return 0;
}

/*
 * Function: mmc reset cards
 * Arg     : host structure
 * Return  : 0 on Success, 1 on Failure
 * Flow    : Reset all the cards to idle condition (CMD 0)
 */
static uint8_t mmc_reset_card(struct sdhci_host *host)
{
	struct mmc_command cmd;

	memset((struct mmc_command *)&cmd, 0, sizeof(struct mmc_command));

	cmd.cmd_index = CMD0_GO_IDLE_STATE;
	cmd.argument = 0;
	cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
	cmd.resp_type = SDHCI_CMD_RESP_NONE;

	/* send command */
	return sdhci_send_command(host, &cmd);
}

/*
 * Function: mmc operations command
 * Arg     : host & card structure
 * Return  : 0 on Success, 1 on Failure
 * Flow    : Send CMD1 to know whether the card supports host VDD profile or not.
 */
static uint32_t mmc_send_op_cond(struct sdhci_host *host, struct mmc_card *card)
{
	struct mmc_command cmd;
	uint32_t mmc_resp = 0;
	uint32_t mmc_ret = 0;
	uint32_t mmc_retry = 0;

	memset((struct mmc_command *)&cmd, 0, sizeof(struct mmc_command));

	/* CMD1 format:
	 * [31] Busy bit
	 * [30:29] Access mode
	 * [28:24] reserved
	 * [23:15] 2.7-3.6
	 * [14:8]  2.0-2.6
	 * [7]     1.7-1.95
	 * [6:0]   reserved
	 */

	cmd.cmd_index = CMD1_SEND_OP_COND;
	cmd.argument = card->ocr;
	cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
	cmd.resp_type = SDHCI_CMD_RESP_R3;

	do {
		mmc_ret = sdhci_send_command(host, &cmd);
		if (mmc_ret)
			return mmc_ret;

		/* Command returned success, now it's time to examine response */
		mmc_resp = cmd.resp[0];

		/* Check the response for busy status */
		if (!(mmc_resp & MMC_OCR_BUSY)) {
			mmc_retry++;
			mdelay(1);
			continue;
		} else
			break;
	} while (mmc_retry < MMC_MAX_COMMAND_RETRY);

	/* If we reached here after max retries, we failed to get OCR */
	if (mmc_retry == MMC_MAX_COMMAND_RETRY && !(mmc_resp & MMC_OCR_BUSY)) {
		dprintf(CRITICAL, "Card has busy status set. Init did not complete\n");
		return 1;
	}

	/* Response contains card's ocr. Update card's information */
	card->ocr = mmc_resp;

	if (mmc_resp & MMC_OCR_SEC_MODE)
		card->type = MMC_TYPE_MMCHC;
	else
		card->type = MMC_TYPE_STD_MMC;

	return 0;
}

/*
 * Function: mmc send cid
 * Arg     : host & card structure
 * Return  : 0 on Success, 1 on Failure
 * Flow    : Request any card to send its uniquie card identification
 *           (CID) number (CMD2).
 */
static uint32_t mmc_all_send_cid(struct sdhci_host *host, struct mmc_card *card)
{
	struct mmc_command cmd;
	uint32_t mmc_ret = 0;

	memset((struct mmc_command *)&cmd, 0, sizeof(struct mmc_command));

	/* CMD2 Format:
	 * [31:0] stuff bits
	 */
	cmd.cmd_index = CMD2_ALL_SEND_CID;
	cmd.argument = 0;
	cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
	cmd.resp_type = SDHCI_CMD_RESP_R2;

	/* send command */
	mmc_ret = sdhci_send_command(host, &cmd);
	if (mmc_ret) {
		return mmc_ret;
	}

	/* Response contains card's 128 bits CID register */
	mmc_ret = mmc_decode_and_save_cid(card, cmd.resp);
	if (mmc_ret) {
		return mmc_ret;
	}

	return 0;
}

/*
 * Function: mmc send relative address
 * Arg     : host & card structure
 * Return  : 0 on Success, 1 on Failure
 * Flow    : Ask card to send it's relative card address (RCA).
 *           This RCA number is shorter than CID and is used by
 *           the host to address the card in future (CMD3)
 */
static uint32_t mmc_send_relative_address(struct sdhci_host *host,
										  struct mmc_card *card)
{
	struct mmc_command cmd;
	uint32_t mmc_ret = 0;

	memset((struct mmc_command *)&cmd, 0, sizeof(struct mmc_command));

	/* CMD3 Format:
	 * [31:0] stuff bits
	 */
	if (MMC_CARD_SD(card)) {
		cmd.cmd_index = CMD3_SEND_RELATIVE_ADDR;
		cmd.argument = 0;
		cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
		cmd.resp_type = SDHCI_CMD_RESP_R6;

		/* send command */
		mmc_ret = sdhci_send_command(host, &cmd);
		if (mmc_ret)
			return mmc_ret;

		/* For sD, card will send RCA. Store it */
		card->rca = (cmd.resp[0] >> 16);
	} else {
		cmd.cmd_index = CMD3_SEND_RELATIVE_ADDR;
		cmd.argument = (MMC_RCA << 16);
		card->rca = (cmd.argument >> 16);
		cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
		cmd.resp_type = SDHCI_CMD_RESP_R6;

		/* send command */
		mmc_ret = sdhci_send_command(host, &cmd);
		if (mmc_ret)
			return mmc_ret;
	}

	return 0;
}

/*
 * Function: mmc send csd
 * Arg     : host, card structure & o/p arg to store csd
 * Return  : 0 on Success, 1 on Failure
 * Flow    : Requests card to send it's CSD register's contents. (CMD9)
 */
static uint32_t mmc_send_csd(struct sdhci_host *host, struct mmc_card *card)
{
	struct mmc_command cmd;
	uint32_t mmc_arg = 0;
	uint32_t mmc_ret = 0;

	memset((struct mmc_command *)&cmd, 0, sizeof(struct mmc_command));

	/* CMD9 Format:
	 * [31:16] RCA
	 * [15:0] stuff bits
	 */
	mmc_arg |= card->rca << 16;

	cmd.cmd_index = CMD9_SEND_CSD;
	cmd.argument = mmc_arg;
	cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
	cmd.resp_type = SDHCI_CMD_RESP_R2;

	/* send command */
	mmc_ret = sdhci_send_command(host, &cmd);
	if (mmc_ret)
		return mmc_ret;

	/* response contains the card csd */
	memcpy(card->raw_csd, cmd.resp, sizeof(cmd.resp));

	return 0;
}

/*
 * Function: mmc select card
 * Arg     : host, card structure
 * Return  : 0 on Success, 1 on Failure
 * Flow    : Selects a card by sending CMD7 to the card with its RCA.
 *           If RCA field is set as 0 ( or any other address ),
 *           the card will be de-selected. (CMD7)
 */
static uint32_t mmc_select_card(struct sdhci_host *host, struct mmc_card *card)
{
	struct mmc_command cmd;
	uint32_t mmc_arg = 0;
	uint32_t mmc_ret = 0;

	memset((struct mmc_command *)&cmd, 0, sizeof(struct mmc_command));

	/* CMD7 Format:
	 * [31:16] RCA
	 * [15:0] stuff bits
	 */
	mmc_arg |= card->rca << 16;

	cmd.cmd_index = CMD7_SELECT_DESELECT_CARD;
	cmd.argument = mmc_arg;
	cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;

	/* If we are deselecting card, we do not get response */
	if (card->rca) {
		if (MMC_CARD_SD(card))
			cmd.resp_type = SDHCI_CMD_RESP_R1B;
		else
			cmd.resp_type = SDHCI_CMD_RESP_R1;
	} else
		cmd.resp_type = SDHCI_CMD_RESP_NONE;

	/* send command */
	mmc_ret = sdhci_send_command(host, &cmd);
	if (mmc_ret)
		return mmc_ret;

	return 0;
}

/*
 * Function: mmc set block len
 * Arg     : host, card structure & block length
 * Return  : 0 on Success, 1 on Failure
 * Flow    : Send command to set block length.
 */
static uint32_t mmc_set_block_len(struct sdhci_host *host,
								  struct mmc_card *card,
								  uint32_t block_len)
{
	struct mmc_command cmd;
	uint32_t mmc_ret = 0;

	memset((struct mmc_command *)&cmd, 0, sizeof(struct mmc_command));

	/* CMD16 Format:
	 * [31:0] block length
	 */

	cmd.cmd_index = CMD16_SET_BLOCKLEN;
	cmd.argument = block_len;
	cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
	cmd.resp_type = SDHCI_CMD_RESP_R1;

	/* send command */
	mmc_ret = sdhci_send_command(host, &cmd);
	if (mmc_ret)
		return mmc_ret;

	/*
	 * If blocklength is larger than 512 bytes,
	 * the card sets BLOCK_LEN_ERROR bit.
	 */
	if (cmd.resp[0] & MMC_R1_BLOCK_LEN_ERR) {
		dprintf(CRITICAL, "The block length is not supported by the card\n");
		return 1;
	}

	return 0;
}

/*
 * Function: mmc get card status
 * Arg     : host, card structure & o/p argument card status
 * Return  : 0 on Success, 1 on Failure
 * Flow    : Get the current status of the card
 */
static uint32_t mmc_get_card_status(struct sdhci_host *host,
									struct mmc_card *card, uint32_t *status)
{
	struct mmc_command cmd;
	uint32_t mmc_ret = 0;

	memset((struct mmc_command *)&cmd, 0, sizeof(struct mmc_command));

	/* CMD13 Format:
	 * [31:16] RCA
	 * [15:0] stuff bits
	 */
	cmd.cmd_index = CMD13_SEND_STATUS;
	cmd.argument = card->rca << 16;
	cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
	cmd.resp_type = SDHCI_CMD_RESP_R1;

	/* send command */
	mmc_ret = sdhci_send_command(host, &cmd);
	if (mmc_ret)
		return mmc_ret;

	/* Checking ADDR_OUT_OF_RANGE error in CMD13 response */
	if ((cmd.resp[0] >> 31) & 0x01)
		return 1;

	*status = cmd.resp[0];
	return 0;
}

/*
 * Function: mmc get ext csd
 * Arg     : host, card structure & array to hold ext attributes
 * Return  : 0 on Success, 1 on Failure
 * Flow    : Send ext csd command & get the card attributes
 */
static uint32_t mmc_get_ext_csd(struct sdhci_host *host, struct mmc_card *card)
{
	struct mmc_command cmd;
	uint32_t mmc_ret = 0;

	card->ext_csd = memalign(CACHE_LINE, ROUNDUP(512, CACHE_LINE));

	ASSERT(card->ext_csd);

	memset(card->ext_csd, 0, 512);

	/* invalidate any cached buf data (controller updates main memory) */
	arch_invalidate_cache_range((addr_t) card->ext_csd, 512);

	memset((struct mmc_command *)&cmd, 0, sizeof(struct mmc_command));

	/* CMD8 */
	cmd.cmd_index = CMD8_SEND_EXT_CSD;
	cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
	cmd.resp_type = SDHCI_CMD_RESP_R1;
	cmd.data.data_ptr = card->ext_csd;
	cmd.data.num_blocks = 1;
	cmd.data_present = 0x1;
	cmd.trans_mode = SDHCI_MMC_READ;

	/* send command */
	mmc_ret = sdhci_send_command(host, &cmd);
	if (mmc_ret)
		return mmc_ret;

	return mmc_ret;
}

/*
 * Function: mmc switch command
 * Arg     : Host, card structure, access mode, index & value to be set
 * Return  : 0 on Success, 1 on Failure
 * Flow    : Send switch command to the card to set the ext attribute @ index
 */
static uint32_t mmc_switch_cmd(struct sdhci_host *host, struct mmc_card *card,
							   uint32_t access, uint32_t index, uint32_t value)
{

	struct mmc_command cmd;
	uint32_t mmc_ret = 0;
	uint32_t mmc_status;

	memset((struct mmc_command *)&cmd, 0, sizeof(struct mmc_command));

	/* CMD6 Format:
	 * [31:26] set to 0
	 * [25:24] access
	 * [23:16] index
	 * [15:8] value
	 * [7:3] set to 0
	 * [2:0] cmd set
	 */
	cmd.cmd_index = CMD6_SWITCH_FUNC;
	cmd.argument |= (access << 24);
	cmd.argument |= (index << 16);
	cmd.argument |= (value << 8);
	cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
	cmd.resp_type = SDHCI_CMD_RESP_R1B;

	mmc_ret = sdhci_send_command(host, &cmd);
	if (mmc_ret) {
		dprintf(CRITICAL, "CMD6 send failed\n");
		return mmc_ret;
	}

	/* Check if the card completed the switch command processing */
	mmc_ret = mmc_get_card_status(host, card, &mmc_status);
	if (mmc_ret) {
		dprintf(CRITICAL, "Get card status failed\n");
		return mmc_ret;
	}

	if (MMC_CARD_STATUS(mmc_status) != MMC_TRAN_STATE) {
		dprintf(CRITICAL, "Switch cmd failed. Card not in tran state %x\n", mmc_status);
		mmc_ret = 1;
	}

	if (mmc_status & MMC_SWITCH_FUNC_ERR_FLAG) {
		dprintf(CRITICAL, "Switch cmd failed. Switch Error.\n");
		mmc_ret = 1;
	}

	return mmc_ret;
}

bool mmc_set_drv_type(struct sdhci_host *host, struct mmc_card *card, uint8_t drv_type)
{
	bool drv_type_changed = false;

	uint32_t value = ((drv_type << 4) | MMC_HS200_TIMING);

	if (MMC_CARD_MMC(card)) {
		if (card->ext_csd[MMC_EXT_MMC_DRV_STRENGTH] & (1 << drv_type)){
			mmc_switch_cmd(host, card, MMC_ACCESS_WRITE, MMC_EXT_MMC_HS_TIMING, value);
			drv_type_changed = true;
		}
	}
	return drv_type_changed;
}
/*
 * Function: mmc set bus width
 * Arg     : Host, card structure & width
 * Return  : 0 on Success, 1 on Failure
 * Flow    : Send switch command to set bus width
 */
static uint32_t mmc_set_bus_width(struct sdhci_host *host,
								  struct mmc_card *card,
								  uint32_t width)
{
	uint32_t mmc_ret = 0;

	mmc_ret = mmc_switch_cmd(host, card, MMC_ACCESS_WRITE,
							 MMC_EXT_MMC_BUS_WIDTH, width);

	if (mmc_ret) {
		dprintf(CRITICAL, "Switch cmd failed\n");
		return mmc_ret;
	}

	return 0;
}


/*
 * Function: mmc card supports hs400 mode
 * Arg     : None
 * Return  : 1 if hs400 mode is supported, 0 otherwise
 * Flow    : Check the ext csd attributes of the card
 */
static uint8_t mmc_card_supports_hs400_mode(struct mmc_card *card)
{
	if (MMC_CARD_MMC(card)) {
		if (card->ext_csd[MMC_DEVICE_TYPE] & MMC_HS_HS400_MODE)
			return 1;
		else
			return 0;
	}
	else
		return 0;
}

/*
 * Function: mmc card supports hs200 mode
 * Arg     : None
 * Return  : 1 if HS200 mode is supported, 0 otherwise
 * Flow    : Check the ext csd attributes of the card
 */
static uint8_t mmc_card_supports_hs200_mode(struct mmc_card *card)
{
	if (MMC_CARD_MMC(card)) {
		if (card->ext_csd[MMC_DEVICE_TYPE] & MMC_HS_HS200_MODE)
			return 1;
		else
			return 0;
	}
	else
		return 0;
}

/*
 * Function: mmc card supports ddr mode
 * Arg     : None
 * Return  : 1 if DDR mode is supported, 0 otherwise
 * Flow    : Check the ext csd attributes of the card
 */
static uint8_t mmc_card_supports_ddr_mode(struct mmc_card *card)
{
	if (MMC_CARD_MMC(card)) {
		if (card->ext_csd[MMC_DEVICE_TYPE] & MMC_HS_DDR_MODE)
			return 1;
		else
			return 0;
	}
	else
		return 0;
}

/*
 * Function : Enable HS200 mode
 * Arg      : Host, card structure and bus width
 * Return   : 0 on Success, 1 on Failure
 * Flow     :
 *           - Set the bus width to 4/8 bit SDR as supported by the target & host
 *           - Set the HS_TIMING on ext_csd 185 for the card
 */
static uint32_t mmc_set_hs200_mode(struct sdhci_host *host,
								   struct mmc_card *card, uint32_t width)
{
	uint32_t mmc_ret = 0;

	DBG("\n Enabling HS200 Mode Start\n");

	/* Set 4/8 bit SDR bus width */
	mmc_ret = mmc_set_bus_width(host, card, width);
	if (mmc_ret) {
		dprintf(CRITICAL, "Failure to set wide bus for Card(RCA:%x)\n",
						  card->rca);
		return mmc_ret;
	}

	/* Setting HS200 in HS_TIMING using EXT_CSD (CMD6) */
	mmc_ret = mmc_switch_cmd(host, card, MMC_ACCESS_WRITE, MMC_EXT_MMC_HS_TIMING, MMC_HS200_TIMING);

	if (mmc_ret) {
		dprintf(CRITICAL, "Switch cmd returned failure %d\n", __LINE__);
		return mmc_ret;
	}

	/* Enable SDR104 mode in controller */
	sdhci_set_uhs_mode(host, SDHCI_SDR104_MODE);

	/* Run the clock @ 400 Mhz */
	if (host->caps.hs400_support && mmc_card_supports_hs400_mode(card))
	{
		/* Save the timing value, before changing the clock */
		MMC_SAVE_TIMING(host, MMC_HS400_TIMING);
		/*
		* Set the MCI_CLK divider before changing the sdcc core
		* core clk to ensure card receives no more than 200 MHZ
		* clock frequency
		*/
		sdhci_msm_set_mci_clk(host);
		clock_config_mmc(host->msm_host->slot, SDHCI_CLK_400MHZ);
	}

	/* Execute Tuning for hs200 mode */
	if ((mmc_ret = sdhci_msm_execute_tuning(host, card, width)))
		dprintf(CRITICAL, "Tuning for hs200 failed\n");

	/* Once the tuning is executed revert back the clock to 200MHZ
	 * and disable the MCI_CLK divider so that we can use SDHC clock
	 * divider to supply clock to the card
	 */
	if (host->timing == MMC_HS400_TIMING)
	{
		MMC_SAVE_TIMING(host, MMC_HS200_TIMING);
		sdhci_msm_set_mci_clk(host);
		clock_config_mmc(host->msm_host->slot, MMC_CLK_192MHZ);
	}
	else
	{
		/* Save the timing value, before changing the clock */
		MMC_SAVE_TIMING(host, MMC_HS200_TIMING);
	}


	DBG("\n Enabling HS200 Mode Done\n");

	return mmc_ret;
}

/*
 * Function: mmc set ddr mode
 * Arg     : Host & card structure
 * Return  : 0 on Success, 1 on Failure
 * Flow    : Set bus width for ddr mode & set controller in DDR mode
*/
static uint8_t mmc_set_ddr_mode(struct sdhci_host *host, struct mmc_card *card)
{
	uint8_t mmc_ret = 0;

	DBG("\n Enabling DDR Mode Start\n");

	/* Set width for 8 bit DDR mode by default */
	mmc_ret = mmc_set_bus_width(host, card, DATA_DDR_BUS_WIDTH_8BIT);

	if (mmc_ret) {
		dprintf(CRITICAL, "Failure to set DDR mode for Card(RCA:%x)\n",
						  card->rca);
		return mmc_ret;
	}

	/* Save the timing value, before changing the clock */
	MMC_SAVE_TIMING(host, SDHCI_DDR50_MODE);

	/* Set the DDR mode in controller */
	sdhci_set_uhs_mode(host, SDHCI_DDR50_MODE);

	DBG("\n Enabling DDR Mode Done\n");

	return 0;
}

/*
 * Function: mmc set high speed interface
 * Arg     : Host & card structure
 * Return  : None
 * Flow    : Sets the sdcc clock & clock divider in the host controller
 *           Adjust the interface speed to optimal speed
 */
static uint32_t mmc_set_hs_interface(struct sdhci_host *host,
									 struct mmc_card *card)
{
	uint32_t mmc_ret = 0;

	/* Setting HS_TIMING in EXT_CSD (CMD6) */
	mmc_ret = mmc_switch_cmd(host, card, MMC_ACCESS_WRITE,
							 MMC_EXT_MMC_HS_TIMING, MMC_HS_TIMING);

	if (mmc_ret) {
		dprintf(CRITICAL, "Switch cmd returned failure %d\n", __LINE__);
		return mmc_ret;
	}

	/* Save the timing value, before changing the clock */
	MMC_SAVE_TIMING(host, SDHCI_SDR25_MODE);

	/* Set the SDR25 mode in controller */
	sdhci_set_uhs_mode(host, SDHCI_SDR25_MODE);

	return 0;
}

/*
 * Function : Enable HS400 mode
 * Arg      : Host, card structure and bus width
 * Return   : 0 on Success, 1 on Failure
 * Flow     :
 *           - Set the bus width to 8 bit DDR
 *           - Set the HS_TIMING on ext_csd 185 for the card
 */
uint32_t mmc_set_hs400_mode(struct sdhci_host *host,
								   struct mmc_card *card, uint32_t width)
{
	uint32_t mmc_ret = 0;

	/*
	 * Emmc 5.0 spec does not allow changing to hs400 mode directly
	 * Need to follow the sequence to change to hs400 mode
	 * 1. Enable HS200 mode, perform tuning
	 * 2. Change to high speed mode
	 * 3. Enable DDR mode
	 * 4. Enable HS400 mode & execute tuning
	 */

	DBG("\n Enabling HS400 Mode Start\n");
	/* HS400 mode is supported only in DDR 8-bit */
	if (width != DATA_BUS_WIDTH_8BIT)
	{
		dprintf(CRITICAL, "Bus width is not 8-bit, cannot switch to hs400: %u\n", width);
		return 1;
	}

	/* 1.Enable HS200 mode */
	mmc_ret = mmc_set_hs200_mode(host, card, width);

	if (mmc_ret)
	{
		dprintf(CRITICAL, "Failure Setting HS200 mode %s\t%d\n",__func__, __LINE__);
		return mmc_ret;
	}

	/* 2. Enable High speed mode */
	/* This is needed to set the clock to a low value &
	 * so that we can switch to hs_timing --> 0x1 */
	/* Save the timing value, before changing the clock */
	MMC_SAVE_TIMING(host, SDHCI_SDR12_MODE);
	sdhci_set_uhs_mode(host, SDHCI_SDR12_MODE);

	/* 3. Set HS_TIMING to 0x1 */
	mmc_ret = mmc_set_hs_interface(host, card);
	if (mmc_ret)
	{
		dprintf(CRITICAL, "Error adjusting interface speed!:%s\t%d\n", __func__, __LINE__);
		return mmc_ret;
	}

	/*4. Enable DDR mode */
	mmc_ret = mmc_set_ddr_mode(host, card);
	if (mmc_ret)
	{
		dprintf(CRITICAL, "Failure setting DDR mode:%s\t%d\n", __func__, __LINE__);
		return mmc_ret;
	}

	/*5. Set hs400 timing */
	mmc_ret = mmc_switch_cmd(host, card, MMC_ACCESS_WRITE, MMC_EXT_MMC_HS_TIMING, MMC_HS400_TIMING);

	if (mmc_ret)
	{
		dprintf(CRITICAL, "Switch cmd returned failure %s\t%d\n",__func__,  __LINE__);
		return mmc_ret;
	}

	/* 6. Enable SDR104 mode in controller */
	/* Save the timing value, before changing the clock */
	MMC_SAVE_TIMING(host, MMC_HS400_TIMING);
	sdhci_set_uhs_mode(host, SDHCI_SDR104_MODE);
	/*
	* Enable HS400 mode
	*/
	sdhci_msm_set_mci_clk(host);
	/* Set the clock back to 400 MHZ */
	clock_config_mmc(host->msm_host->slot, SDHCI_CLK_400MHZ);

	/* 7. Execute Tuning for hs400 mode */
	if ((mmc_ret = sdhci_msm_execute_tuning(host, card, width)))
		dprintf(CRITICAL, "Tuning for hs400 failed\n");

	DBG("\n Enabling HS400 Mode Done\n");

	return mmc_ret;
}

/*
 * Function: mmc_host_init
 * Arg     : mmc device structure
 * Return  : 0 on success, 1 on Failure
 * Flow    : Initialize the host contoller
 *           Set the clock rate to 400 KHZ for init
 */
static uint8_t mmc_host_init(struct mmc_device *dev)
{
	uint8_t mmc_ret = 0;

	struct sdhci_host *host;
	struct mmc_config_data *cfg;
	struct sdhci_msm_data *data;

	event_t sdhc_event;

	host = &dev->host;
	cfg = &dev->config;

	event_init(&sdhc_event, false, EVENT_FLAG_AUTOUNSIGNAL);

	host->base = cfg->sdhc_base;
	host->sdhc_event = &sdhc_event;
	host->caps.hs200_support = cfg->hs200_support;
	host->caps.hs400_support = cfg->hs400_support;

	data = (struct sdhci_msm_data *) malloc(sizeof(struct sdhci_msm_data));
	ASSERT(data);

	data->sdhc_event = &sdhc_event;
	data->pwrctl_base = cfg->pwrctl_base;
	data->pwr_irq = cfg->pwr_irq;
	data->slot = cfg->slot;
	data->use_io_switch = cfg->use_io_switch;

	host->msm_host = data;

	/* Initialize any clocks needed for SDC controller */
	clock_init_mmc(cfg->slot);

	clock_config_mmc(cfg->slot, cfg->max_clk_rate);

	/* Configure the CDC clocks needed for emmc storage
	 * we use slot '1' for emmc
	 */
	if (cfg->slot == 1)
		clock_config_cdc(cfg->slot);

	/*
	 * MSM specific sdhc init
	 */
	sdhci_msm_init(host, data);

	/*
	 * Initialize the controller, read the host capabilities
	 * set power on mode
	 */
	sdhci_init(host);

	/* Setup initial freq to 400KHz */
	mmc_ret = sdhci_clk_supply(host, SDHCI_CLK_400KHZ);

	return mmc_ret;
}

/*
 * Function: mmc identify card
 * Arg     : host & card structure
 * Return  : 0 on Success, 1 on Failure
 * Flow    : Performs card identification process:
 *           1. Get card's unique identification number (CID)
 *           2. Get(for sd)/set (for mmc) relative card address (RCA)
 *           3. Select the card to put it in TRAN state
 */
static uint32_t mmc_identify_card(struct sdhci_host *host, struct mmc_card *card)
{
	uint32_t mmc_return = 0;

	/* Ask card to send its unique card identification (CID) number (CMD2) */
	mmc_return = mmc_all_send_cid(host, card);
	if (mmc_return) {
		dprintf(CRITICAL,"Failure getting card's CID number!\n");
		return mmc_return;
	}

	/* Ask card to send a relative card address (RCA) (CMD3) */
	mmc_return = mmc_send_relative_address(host, card);
	if (mmc_return) {
		dprintf(CRITICAL, "Failure getting card's RCA!\n");
		return mmc_return;
	}

	/* Get card's CSD register (CMD9) */
	mmc_return = mmc_send_csd(host, card);
	if (mmc_return) {
		dprintf(CRITICAL,"Failure getting card's CSD information!\n");
		return mmc_return;
	}

	/* Select the card (CMD7) */
	mmc_return = mmc_select_card(host, card);
	if (mmc_return) {
		dprintf(CRITICAL, "Failure selecting the Card with RCA: %x\n",card->rca);
		return mmc_return;
	}

	/* Set the card status as active */
	card->status = MMC_STATUS_ACTIVE;

	return 0;
}

/*
 * Function: mmc_reset_card_and_send_op
 * Arg     : Host & Card structure
 * Return  : 0 on Success, 1 on Failure
 * Flow    : Routine to initialize MMC card. It resets a card to idle state,
 *           verify operating voltage and set the card in ready state.
 */
static uint32_t mmc_reset_card_and_send_op(struct sdhci_host *host, struct mmc_card *card)
{
	uint32_t mmc_return = 0;

	/* 1. Card Reset - CMD0 */
	mmc_return = mmc_reset_card(host);
	if (mmc_return) {
		dprintf(CRITICAL, "Failure resetting MMC cards!\n");
		return mmc_return;
	}

	/* 2. Card Initialization process */

	/*
	 * Send CMD1 to identify and reject cards that do not match host's VDD range
	 * profile. Cards sends its OCR register in response.
	 */

	mmc_return = mmc_send_op_cond(host, card);

	/* OCR is not received, init could not complete */
	if (mmc_return) {
		dprintf(CRITICAL, "Failure getting OCR response from MMC Card\n");
		return mmc_return;
	}

	return 0;
}

static uint32_t mmc_send_app_cmd(struct sdhci_host *host, struct mmc_card *card)
{
	struct mmc_command cmd = {0};

	cmd.cmd_index = CMD55_APP_CMD;
	cmd.argument = (card->rca << 16);
	cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
	cmd.resp_type = SDHCI_CMD_RESP_R1;

	if (sdhci_send_command(host, &cmd))
	{
		dprintf(CRITICAL, "Failed Sending CMD55\n");
		return 1;
	}
	return 0;
}

uint32_t mmc_sd_card_init(struct sdhci_host *host, struct mmc_card *card)
{
	uint8_t i;
	struct mmc_command cmd;

	memset((struct mmc_command *)&cmd, 0, sizeof(struct mmc_command));

	/* Use the SD card RCA 0x0 during init */
	card->rca = SD_CARD_RCA;

	/* Send CMD8 for voltage check*/
	for (i = 0 ;i < SD_CMD8_MAX_RETRY; i++)
	{
		cmd.cmd_index = CMD8_SEND_IF_COND;
		cmd.argument = MMC_SD_HC_VOLT_SUPPLIED;
		cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
		cmd.resp_type = SDHCI_CMD_RESP_R7;

		if (sdhci_send_command(host, &cmd))
		{
			dprintf(CRITICAL, "The response for CMD8 does not match the supplied value\n");
			return 1;
		}
		else
		{
			/* If the command response echos the voltage back */
			if (cmd.resp[0] == MMC_SD_HC_VOLT_SUPPLIED)
				break;
		}
		/* As per SDCC the spec try for max three times with
		 * 1 ms delay
		 */
		mdelay(1);
	}

	if (i == SD_CMD8_MAX_RETRY && (cmd.resp[0] != MMC_SD_HC_VOLT_SUPPLIED))
	{
		dprintf(CRITICAL, "Error: CMD8 response timed out\n");
		return 1;
	}

	/* Send ACMD41 for OCR */
	for (i = 0; i < SD_ACMD41_MAX_RETRY; i++)
	{
		/* Send APP_CMD before ACMD41*/
		if (mmc_send_app_cmd(host, card))
		{
			dprintf(CRITICAL, "Failed sending App command\n");
			return 1;
		}

		/* APP_CMD is successful, send ACMD41 now */
		cmd.cmd_index = ACMD41_SEND_OP_COND;
		cmd.argument = MMC_SD_OCR | MMC_SD_HC_HCS;
		cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
		cmd.resp_type = SDHCI_CMD_RESP_R3;

		if (sdhci_send_command(host, &cmd))
		{
			dprintf(CRITICAL, "Failure sending ACMD41\n");
			return 1;
		}
		else
		{
			if (cmd.resp[0] & MMC_SD_DEV_READY)
			{
				if (cmd.resp[0] & (1 << 30))
					card->type = MMC_CARD_TYPE_SDHC;
				else
					card->type = MMC_CARD_TYPE_STD_SD;

				break;
			}
		}
		/*
		 * As per SDCC spec try for max 1 second
		 */
		mdelay(50);
	}

	if (i == SD_ACMD41_MAX_RETRY && !(cmd.resp[0] & MMC_SD_DEV_READY))
	{
		dprintf(CRITICAL, "Error: ACMD41 response timed out\n");
		return 1;
	}

	return 0;
}

/*
 * Function to read SD card information from SD status
 */
static uint32_t mmc_sd_get_card_ssr(struct sdhci_host *host, struct mmc_card *card)
{
	BUF_DMA_ALIGN(raw_sd_status, 64);
	struct mmc_command cmd = {0};
	uint32_t sd_status[16];
	uint32_t *status = sd_status;
	uint32_t au_size;
	int i;
	int j;

	if (mmc_send_app_cmd(host, card))
	{
		dprintf(CRITICAL, "Failed sending App command\n");
		return 1;
	}

	cmd.cmd_index = ACMD13_SEND_SD_STATUS;
	cmd.argument = 0x0;
	cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
	cmd.resp_type = SDHCI_CMD_RESP_R1;
	cmd.trans_mode = SDHCI_MMC_READ;
	cmd.data_present = 0x1;
	cmd.data.data_ptr = raw_sd_status;
	cmd.data.num_blocks = 0x1;
	cmd.data.blk_sz = 0x40;

	/* send command */
	if (sdhci_send_command(host, &cmd))
		return 1;

	memcpy(sd_status, raw_sd_status, sizeof(sd_status));

	for (i = 15, j = 0; i >=0 ; i--, j++)
		sd_status[i] = swap_endian32(sd_status[j]);

	au_size = UNPACK_BITS(status, MMC_SD_AU_SIZE_BIT, MMC_SD_AU_SIZE_LEN, 32);
	/* Card AU size in sectors */
	card->ssr.au_size = 1 << (au_size + 4);
	card->ssr.num_aus = UNPACK_BITS(status, MMC_SD_ERASE_SIZE_BIT, MMC_SD_ERASE_SIZE_LEN, 32);
	/*if num_aus is 0 then host should assign number of AU erased at a time*/
	if (!card->ssr.num_aus)
		card->ssr.num_aus = 0x10;
	return 0;
}

/*
 * Function to read the SD CARD configuration register
 */
static uint32_t mmc_sd_get_card_scr(struct sdhci_host *host, struct mmc_card *card)
{
	BUF_DMA_ALIGN(scr_resp, 8);
	struct mmc_command cmd = {0};
	uint32_t raw_scr[2];

	/* Now read the SCR register */
	/* Send APP_CMD before ACMD51*/
	if (mmc_send_app_cmd(host, card))
	{
		dprintf(CRITICAL, "Failed sending App command\n");
		return 1;
	}

	cmd.cmd_index = ACMD51_READ_CARD_SCR;
	cmd.argument = 0x0;
	cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
	cmd.resp_type = SDHCI_CMD_RESP_R1;
	cmd.trans_mode = SDHCI_MMC_READ;
	cmd.data_present = 0x1;
	cmd.data.data_ptr = scr_resp;
	cmd.data.num_blocks = 0x1;
	cmd.data.blk_sz = 0x8;

	/* send command */
	if (sdhci_send_command(host, &cmd))
		return 1;

	memcpy(raw_scr, scr_resp, sizeof(raw_scr));

	card->raw_scr[0] = swap_endian32(raw_scr[0]);
	card->raw_scr[1] = swap_endian32(raw_scr[1]);

	/*
	 * Parse & Populate the SCR data as per sdcc spec
	 */
	card->scr.bus_widths = (card->raw_scr[0] & SD_SCR_BUS_WIDTH_MASK) >> SD_SCR_BUS_WIDTH;
	card->scr.cmd23_support = (card->raw_scr[0] & SD_SCR_CMD23_SUPPORT);
	card->scr.sd_spec = (card->raw_scr[0] & SD_SCR_SD_SPEC_MASK) >> SD_SCR_SD_SPEC;
	card->scr.sd3_spec = (card->raw_scr[0] & SD_SCR_SD_SPEC3_MASK) >> SD_SCR_SD_SPEC3;

	return 0;
}

/*
 * Function: mmc_set_sd_bus_width
 * Arg     : host, device structure & width
 * Return  : 0 on Success, 1 on Failure
 * Flow    : Set the bus width for the card
 */
uint32_t mmc_sd_set_bus_width(struct sdhci_host *host, struct mmc_card *card, uint8_t width)
{
	struct mmc_command cmd = {0};

	/* Send APP_CMD before ACMD6*/
	if (mmc_send_app_cmd(host, card))
	{
		dprintf(CRITICAL, "Failed sending App command\n");
		return 1;
	}

	cmd.cmd_index = ACMD6_SET_BUS_WIDTH;
	cmd.argument = (width == DATA_BUS_WIDTH_4BIT) ? (1<<1) : 0;
	cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
	cmd.resp_type = SDHCI_CMD_RESP_R1;

	/* send command */
	if (sdhci_send_command(host, &cmd))
		return 1;

	return 0;
}

uint32_t mmc_sd_set_hs(struct sdhci_host *host, struct mmc_card *card)
{
       struct mmc_command cmd = {0};
       BUF_DMA_ALIGN(switch_resp, 64);

       cmd.cmd_index = CMD6_SWITCH_FUNC;
       cmd.argument = MMC_SD_SWITCH_HS;
       cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
       cmd.resp_type = SDHCI_CMD_RESP_R1;
       cmd.trans_mode = SDHCI_MMC_READ;
       cmd.data_present = 0x1;
       cmd.data.data_ptr = switch_resp;
       cmd.data.num_blocks = 0x1;
       cmd.data.blk_sz = 0x40;

       /* send command */
       if (sdhci_send_command(host, &cmd))
             return 1;

	/* Set the SDR25 mode in controller*/
	sdhci_set_uhs_mode(host, SDHCI_SDR25_MODE);

	return 0;
}

static const char *mmc_card_type_str(struct mmc_card *card)
{
	switch (card->type) {
	case MMC_TYPE_STD_SD:
	case MMC_TYPE_SDHC:
		return "SD";
	case MMC_TYPE_SDIO:
		return "SDIO";
	case MMC_TYPE_MMCHC:
	case MMC_TYPE_STD_MMC:
		return "MMC";
	default:
		return "UNKNOWN";
	}
}

/*
 * Function: mmc_init_card
 * Arg     : mmc device structure
 * Return  : 0 on Success, 1 on Failure
 * Flow    : Performs initialization and identification of eMMC cards connected
 *           to the host.
 */

static uint32_t mmc_card_init(struct mmc_device *dev)
{
	uint32_t mmc_return = 0;
	uint8_t bus_width = 0;

	struct sdhci_host *host;
	struct mmc_card *card;
	struct mmc_config_data *cfg;

	host = &dev->host;
	card = &dev->card;
	cfg  = &dev->config;

	/* Initialize MMC card structure */
	card->status = MMC_STATUS_INACTIVE;

	/* TODO: Get the OCR params from target */
	card->ocr = MMC_OCR_27_36 | MMC_OCR_SEC_MODE;

	/* Initialize the internal MMC */
	mmc_return = mmc_reset_card_and_send_op(host, card);
	if (mmc_return)
	{
		dprintf(CRITICAL, "MMC card failed to respond, try for SD card\n");
		/* Reset the card & get the OCR */
		mmc_return = mmc_sd_card_init(host, card);
		if (mmc_return)
		{
			dprintf(CRITICAL, "Failed to initialize SD card\n");
			return mmc_return;
		}
	}

	/* Identify (CMD2, CMD3 & CMD9) and select the card (CMD7) */
	mmc_return = mmc_identify_card(host, card);
	if (mmc_return)
		return mmc_return;

	/* set interface speed */
	if (MMC_CARD_SD(card))
	{
		mmc_return = mmc_sd_set_hs(host, card);
		if (mmc_return)
		{
			dprintf(CRITICAL, "Failed to set HS for SD card\n");
			return mmc_return;
		}
	}
	else
	{
		mmc_return = mmc_set_hs_interface(host, card);
		if (mmc_return) {
			dprintf(CRITICAL, "Error adjusting interface speed!\n");
			return mmc_return;
		}
	}

	/* Now get the extended CSD for the card */
	if (MMC_CARD_MMC(card))
	{
			/* For MMC cards, also get the extended csd */
			mmc_return = mmc_get_ext_csd(host, card);

			if (mmc_return) {
				dprintf(CRITICAL, "Failure getting card's ExtCSD information!\n");
				return mmc_return;
			}
	}
	else
	{
		/*Read SCR for sd card */
		if (mmc_sd_get_card_scr(host, card))
		{
			dprintf(CRITICAL, "Failure getting card's SCR register\n");
			return 1;
		}
		/* Read SSR for the SD card */
		if (mmc_sd_get_card_ssr(host, card))
		{
			dprintf(CRITICAL, "Failed to get SSR from the card\n");
			return 1;
		}
	}

	/* Decode and save the CSD register */
	mmc_return = mmc_decode_and_save_csd(card);
	if (mmc_return) {
		dprintf(CRITICAL, "Failure decoding card's CSD information!\n");
		return mmc_return;
	}

	dprintf(INFO, "%s card: %s (%d.%d, %02d %04d), manufacturer: %x, OEM: %x, "
		"capacity: %llu bytes\n", mmc_card_type_str(card),
		card->cid.pnm, card->cid.prv >> 4, card->cid.prv & 0xF,
		card->cid.month, card->cid.year, card->cid.mid, card->cid.oid,
		card->capacity);

	if (MMC_CARD_MMC(card))
	{
		/* Set the bus width based on host, target capbilities */
		if (cfg->bus_width == DATA_BUS_WIDTH_8BIT && host->caps.bus_width_8bit)
				bus_width = DATA_BUS_WIDTH_8BIT;
		/*
		 * Host contoller by default supports 4 bit & 1 bit mode.
		 * No need to check for host support here
		 */
		else if (cfg->bus_width == DATA_BUS_WIDTH_4BIT)
				bus_width = DATA_BUS_WIDTH_4BIT;
		else
				bus_width = DATA_BUS_WIDTH_1BIT;

		/* Set 4/8 bit SDR bus width in controller */
		mmc_return = sdhci_set_bus_width(host, bus_width);

		if (mmc_return) {
			dprintf(CRITICAL, "Failed to set bus width for host controller\n");
			return 1;
		}

		/* Enable high speed mode in the follwing order:
		 * 1. HS400 mode if supported by host & card
		 * 1. HS200 mode if supported by host & card
		 * 2. DDR mode host, if supported by host & card
		 * 3. Use normal speed mode with supported bus width
		 */
		if (host->caps.hs400_support && mmc_card_supports_hs400_mode(card))
		{
			dprintf(INFO, "SDHC Running in HS400 mode\n");
			mmc_return = mmc_set_hs400_mode(host, card, bus_width);
			if (mmc_return)
			{
				dprintf(CRITICAL, "Failure to set HS400 mode for Card(RCA:%x)\n",
								  card->rca);
				return mmc_return;
			}
		}
#if USE_TARGET_HS200_CAPS
		else if (host->caps.hs200_support && host->caps.sdr104_support && mmc_card_supports_hs200_mode(card))
#else
		else if (host->caps.sdr104_support && mmc_card_supports_hs200_mode(card))
#endif
		{
			dprintf(INFO, "SDHC Running in HS200 mode\n");
			mmc_return = mmc_set_hs200_mode(host, card, bus_width);

			if (mmc_return) {
				dprintf(CRITICAL, "Failure to set HS200 mode for Card(RCA:%x)\n",
								  card->rca);
				return mmc_return;
			}
		} else if (host->caps.ddr_support && mmc_card_supports_ddr_mode(card)) {
			dprintf(INFO, "SDHC Running in DDR mode\n");
			mmc_return = mmc_set_ddr_mode(host, card);

			if (mmc_return) {
				dprintf(CRITICAL, "Failure to set DDR mode for Card(RCA:%x)\n",
								  card->rca);
				return mmc_return;
			}
		} else {
			dprintf(INFO, "SDHC Running in High Speed mode\n");
			/* Set HS_TIMING mode */
			mmc_return = mmc_set_hs_interface(host, card);
			if (mmc_return) {
				dprintf(CRITICAL, "Failure to enalbe HS mode for Card(RCA:%x)\n",
								  card->rca);
				return mmc_return;
			}
			/* Set wide bus mode */
			mmc_return = mmc_set_bus_width(host, card, bus_width);
			if (mmc_return) {
				dprintf(CRITICAL, "Failure to set wide bus for Card(RCA:%x)\n",
								  card->rca);
				return mmc_return;
			}
		}
	}
	else
	{
		/* Check the supported bus width for the card from SCR register */
		if (card->scr.bus_widths & SD_SCR_WIDTH_4BIT)
			bus_width = DATA_BUS_WIDTH_4BIT;
		else
			bus_width = DATA_BUS_WIDTH_1BIT;

		mmc_return = mmc_sd_set_bus_width(host, card, bus_width);
		if (mmc_return)
		{
			dprintf(CRITICAL, "Failed to set bus width for the card\n");
			return mmc_return;
		}

		/* Set bit SDR bus width in controller */
		mmc_return = sdhci_set_bus_width(host, bus_width);
		if (mmc_return)
		{
			dprintf(CRITICAL, "Failed to set bus width for host controller\n");
			return mmc_return;
		}
	}


	card->block_size = MMC_BLK_SZ;

	if (MMC_CARD_MMC(card)) {
		/* Enable RST_n_FUNCTION */
		if (!card->ext_csd[MMC_EXT_CSD_RST_N_FUNC])
		{
			mmc_return = mmc_switch_cmd(host, card, MMC_SET_BIT, MMC_EXT_CSD_RST_N_FUNC, RST_N_FUNC_ENABLE);

			if (mmc_return)
			{
				dprintf(CRITICAL, "Failed to enable RST_n_FUNCTION\n");
				return mmc_return;
			}
		}

	}
	return mmc_return;
}

/*
 * Function: mmc display csd
 * Arg     : None
 * Return  : None
 * Flow    : Displays the csd information
 */
static void mmc_display_csd(struct mmc_card *card)
{
	dprintf(SPEW, "erase_grpsize: %d\n", card->csd.erase_grp_size);
	dprintf(SPEW, "erase_grpmult: %d\n", card->csd.erase_grp_mult);
	dprintf(SPEW, "wp_grpsize: %d\n", card->csd.wp_grp_size);
	dprintf(SPEW, "wp_grpen: %d\n", card->csd.wp_grp_enable);
	dprintf(SPEW, "perm_wp: %d\n", card->csd.perm_wp);
	dprintf(SPEW, "temp_wp: %d\n", card->csd.temp_wp);
}

/*
 * Function: mmc_init
 * Arg     : MMC configuration data
 * Return  : Pointer to mmc device
 * Flow    : Entry point to MMC boot process
 *           Initialize the sd host controller
 *           Initialize the mmc card
 *           Set the clock & high speed mode
 */
struct mmc_device *mmc_init(struct mmc_config_data *data)
{
	uint8_t mmc_ret = 0;
	struct mmc_device *dev;

	dev = (struct mmc_device *) malloc (sizeof(struct mmc_device));

	if (!dev) {
		dprintf(CRITICAL, "Error allocating mmc device\n");
		return NULL;
	}

	ASSERT(data);

	memcpy((void*)&dev->config, (void*)data, sizeof(struct mmc_config_data));

	memset((struct mmc_card *)&dev->card, 0, sizeof(struct mmc_card));

	/* Initialize the host & clock */
	dprintf(SPEW, " Initializing MMC host data structure and clock!\n");

	mmc_ret = mmc_host_init(dev);
	if (mmc_ret) {
		dprintf(CRITICAL, "Error Initializing MMC host : %u\n", mmc_ret);
		return NULL;
	}

	/* Initialize and identify cards connected to host */
	mmc_ret = mmc_card_init(dev);
	if (mmc_ret) {
		dprintf(CRITICAL, "Failed detecting MMC/SDC @ slot%d\n",
						  dev->config.slot);
		return NULL;
	}

	dprintf(INFO, "Done initialization of the card\n");

	mmc_display_csd(&dev->card);

	return dev;
}

static uint32_t mmc_parse_response(uint32_t resp)
{
	/* Trying to write beyond card capacity */
	if (resp & MMC_R1_ADDR_OUT_OF_RANGE) {
		dprintf(CRITICAL, "Attempting to read or write beyond the Device capacity\n");
		return 1;
	}

	/* Misaligned address not matching block length */
	if (resp & MMC_R1_ADDR_ERR) {
		dprintf(CRITICAL, "The misaligned address did not match the block length used\n");
		return 1;
	}

	/* Invalid block length */
	if (resp & MMC_R1_BLOCK_LEN_ERR) {
		dprintf(CRITICAL, "The transferred bytes does not match the block length\n");
		return 1;
	}

	/* Tried to program write protected block */
	if (resp & MMC_R1_WP_VIOLATION) {
		dprintf(CRITICAL, "Attempt to program a write protected block\n");
		return 1;
	}

	/* card controller error */
	if (resp & MMC_R1_CC_ERROR) {
		dprintf(CRITICAL, "Device error occurred, which is not related to the host command\n");
		return 1;
	}

	/* Generic error */
	if (resp & MMC_R1_GENERIC_ERR) {
		dprintf(CRITICAL, "A generic Device error\n");
		return 1;
	}

	/* Finally check for card in TRAN state */
	if (MMC_CARD_STATUS(resp) != MMC_TRAN_STATE) {
		dprintf(CRITICAL, "MMC card is not in TRAN state\n");
		return 1;
	}

	return 0;
}

static uint32_t mmc_stop_command(struct mmc_device *dev)
{
	struct mmc_command cmd;
	uint32_t mmc_ret = 0;

	memset((struct mmc_command *)&cmd, 0, sizeof(struct mmc_command));

	cmd.cmd_index = CMD12_STOP_TRANSMISSION;
	cmd.argument = (dev->card.rca << 16);
	cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
	cmd.resp_type = SDHCI_CMD_RESP_R1;

	mmc_ret = sdhci_send_command(&dev->host, &cmd);
	if(mmc_ret)
	{
		dprintf(CRITICAL, "Failed to send stop command\n");
		return mmc_ret;
	}

	/* Response contains 32 bit Card status.
	 * Parse the errors & provide relevant information */

	return mmc_parse_response(cmd.resp[0]);
}

/*
 * Function: mmc sdhci read
 * Arg     : mmc device structure, block address, number of blocks & destination
 * Return  : 0 on Success, non zero on success
 * Flow    : Fill in the command structure & send the command
 */
uint32_t mmc_sdhci_read(struct mmc_device *dev, void *dest,
						uint64_t blk_addr, uint32_t num_blocks)
{
	uint32_t mmc_ret = 0;
	struct mmc_command cmd;
	struct mmc_card *card = &dev->card;

	memset((struct mmc_command *)&cmd, 0, sizeof(struct mmc_command));

	/* CMD17/18 Format:
	 * [31:0] Data Address
	 */
	if (num_blocks == 1)
		cmd.cmd_index = CMD17_READ_SINGLE_BLOCK;
	else
		cmd.cmd_index = CMD18_READ_MULTIPLE_BLOCK;

	/*
	 * Standard emmc cards use byte mode addressing
	 * convert the block address to byte address before
	 * sending the command
	 */
	if (card->type == MMC_TYPE_STD_MMC)
		cmd.argument = blk_addr * card->block_size;
	else
		cmd.argument = blk_addr;

	cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
	cmd.resp_type = SDHCI_CMD_RESP_R1;
	cmd.trans_mode = SDHCI_MMC_READ;
	cmd.data_present = 0x1;

	/* Use CMD23 If card supports CMD23:
	 * For SD card use the value read from SCR register
	 * For emmc by default use CMD23.
	 * Also as per SDCC spec always use CMD23 to stop
	 * multiblock read/write if UHS (Ultra High Speed) is
	 * enabled
	 */
	if (MMC_CARD_SD(card))
		cmd.cmd23_support = dev->card.scr.cmd23_support;
	else
		cmd.cmd23_support = 0x1;

	cmd.data.data_ptr = dest;
	cmd.data.num_blocks = num_blocks;

	/* send command */
	mmc_ret = sdhci_send_command(&dev->host, &cmd);

	/* For multi block read failures send stop command */
	if (mmc_ret && num_blocks > 1)
	{
		return mmc_stop_command(dev);
	}

	/*
	 * Response contains 32 bit Card status.
	 * Parse the errors & provide relevant information
	 */
	return mmc_parse_response(cmd.resp[0]);
}

/*
 * Function: mmc sdhci write
 * Arg     : mmc device structure, block address, number of blocks & source
 * Return  : 0 on Success, non zero on success
 * Flow    : Fill in the command structure & send the command
 */
uint32_t mmc_sdhci_write(struct mmc_device *dev, void *src,
						 uint64_t blk_addr, uint32_t num_blocks)
{
	uint32_t mmc_ret = 0;
	struct mmc_command cmd;
	struct mmc_card *card = &dev->card;

	memset((struct mmc_command *)&cmd, 0, sizeof(struct mmc_command));

	/* CMD24/25 Format:
	 * [31:0] Data Address
	 */

	if (num_blocks == 1)
		cmd.cmd_index = CMD24_WRITE_SINGLE_BLOCK;
	else
		cmd.cmd_index = CMD25_WRITE_MULTIPLE_BLOCK;

	/*
	 * Standard emmc cards use byte mode addressing
	 * convert the block address to byte address before
	 * sending the command
	 */
	if (card->type == MMC_TYPE_STD_MMC)
		cmd.argument = blk_addr * card->block_size;
	else
		cmd.argument = blk_addr;
	cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
	cmd.resp_type = SDHCI_CMD_RESP_R1;
	cmd.trans_mode = SDHCI_MMC_WRITE;

	/* Use CMD23 If card supports CMD23:
	 * For SD card use the value read from SCR register
	 * For emmc by default use CMD23.
	 * Also as per SDCC spec always use CMD23 to stop
	 * multiblock read/write if UHS (Ultra High Speed) is
	 * enabled
	 */
	if (MMC_CARD_SD(card))
		cmd.cmd23_support = dev->card.scr.cmd23_support;
	else
		cmd.cmd23_support = 0x1;

	cmd.data_present = 0x1;
	cmd.data.data_ptr = src;
	cmd.data.num_blocks = num_blocks;

	/* send command */
	mmc_ret = sdhci_send_command(&dev->host, &cmd);

	/* For multi block write failures send stop command */
	if (mmc_ret && num_blocks > 1)
	{
		return mmc_stop_command(dev);
	}

	/*
	 * Response contains 32 bit Card status.
	 * Parse the errors & provide relevant information
	 */
	return mmc_parse_response(cmd.resp[0]);
}

/*
 * Send the erase group start address using CMD35
 */
static uint32_t mmc_send_erase_grp_start(struct mmc_device *dev, uint32_t erase_start)
{
	struct mmc_command cmd;
	struct mmc_card *card = &dev->card;

	memset((struct mmc_command *)&cmd, 0, sizeof(struct mmc_command));

	if (MMC_CARD_MMC(card))
		cmd.cmd_index = CMD35_ERASE_GROUP_START;
	else
		cmd.cmd_index = CMD32_ERASE_WR_BLK_START;

	/*
	 * Standard emmc cards use byte mode addressing
	 * convert the block address to byte address before
	 * sending the command
	 */
	if (card->type == MMC_TYPE_STD_MMC)
		cmd.argument = erase_start * card->block_size;
	else
		cmd.argument = erase_start;
	cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
	cmd.resp_type = SDHCI_CMD_RESP_R1;

	/* send command */
	if (sdhci_send_command(&dev->host, &cmd))
		return 1;

	/*
	 * CMD35 on failure returns address out of range error
	 */
	if (MMC_ADDR_OUT_OF_RANGE(cmd.resp[0]))
	{
		dprintf(CRITICAL, "Address for CMD35 is out of range\n");
		return 1;
	}

	return 0;
}

/*
 * Send the erase group end address using CMD36
 */
static uint32_t mmc_send_erase_grp_end(struct mmc_device *dev, uint32_t erase_end)
{
	struct mmc_command cmd;
	struct mmc_card *card = &dev->card;

	memset((struct mmc_command *)&cmd, 0, sizeof(struct mmc_command));

	if (MMC_CARD_MMC(card))
		cmd.cmd_index = CMD36_ERASE_GROUP_END;
	else
		cmd.cmd_index = CMD33_ERASE_WR_BLK_END;

	/*
	 * Standard emmc cards use byte mode addressing
	 * convert the block address to byte address before
	 * sending the command
	 */
	if (card->type == MMC_TYPE_STD_MMC)
		cmd.argument = erase_end * card->block_size;
	else
		cmd.argument = erase_end;
	cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
	cmd.resp_type = SDHCI_CMD_RESP_R1;

	/* send command */
	if (sdhci_send_command(&dev->host, &cmd))
		return 1;

	/*
	 * CMD3 on failure returns address out of range error
	 */
	if (MMC_ADDR_OUT_OF_RANGE(cmd.resp[0]))
	{
		dprintf(CRITICAL, "Address for CMD36 is out of range\n");
		return 1;
	}

	return 0;
}

/*
 * Send the erase CMD38, to erase the selected erase groups
 */
static uint32_t mmc_send_erase(struct mmc_device *dev, uint64_t erase_timeout)
{
	struct mmc_command cmd;
	uint32_t status;
	uint32_t retry = 0;

	memset((struct mmc_command *)&cmd, 0, sizeof(struct mmc_command));

	cmd.cmd_index = CMD38_ERASE;
	cmd.argument = 0x00000000;
	cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
	cmd.resp_type = SDHCI_CMD_RESP_R1B;
	cmd.cmd_timeout = erase_timeout;

	/* send command */
	if (sdhci_send_command(&dev->host, &cmd))
		return 1;

	do
	{
		if (mmc_get_card_status(&dev->host, &dev->card, &status))
		{
			dprintf(CRITICAL, "Failed to get card status after erase\n");
			return 1;
		}
		/* Check if the response of erase command has eras skip status set */
		if (status & MMC_R1_WP_ERASE_SKIP)
			dprintf(CRITICAL, "Write Protect set for the region, only partial space was erased\n");

		retry++;
		udelay(1000);
		if (retry == MMC_MAX_CARD_STAT_RETRY)
		{
			dprintf(CRITICAL, "Card status check timed out after sending erase command\n");
			return 1;
		}
	} while(!(status & MMC_READY_FOR_DATA) || (MMC_CARD_STATUS(status) == MMC_PROG_STATE));


	return 0;
}


/*
 * Function: mmc sdhci erase
 * Arg     : mmc device structure, block address and length
 * Return  : 0 on Success, non zero on failure
 * Flow    : Fill in the command structure & send the command
 */
uint32_t mmc_sdhci_erase(struct mmc_device *dev, uint32_t blk_addr, uint64_t len)
{
	uint32_t erase_unit_sz = 0;
	uint32_t erase_start;
	uint32_t erase_end;
	uint32_t blk_end;
	uint32_t num_erase_grps;
	uint64_t erase_timeout = 0;
	struct mmc_card *card;


	card = &dev->card;

	/*
	 * Calculate the erase unit size,
	 * 1. Based on emmc 4.5 spec for emmc card
	 * 2. Use SD Card Status info for SD cards
	 */
	if (MMC_CARD_MMC(card))
	{
		/*
		 * Calculate the erase unit size as per the emmc specification v4.5
		 */
		if (dev->card.ext_csd[MMC_ERASE_GRP_DEF])
			erase_unit_sz = (MMC_HC_ERASE_MULT * dev->card.ext_csd[MMC_HC_ERASE_GRP_SIZE]) / MMC_BLK_SZ;
		else
			erase_unit_sz = (dev->card.csd.erase_grp_size + 1) * (dev->card.csd.erase_grp_mult + 1);
	}
	else
		erase_unit_sz = dev->card.ssr.au_size * dev->card.ssr.num_aus;


	/* Convert length in blocks */
	len = len / MMC_BLK_SZ;

	if (len < erase_unit_sz)
	{
		dprintf(CRITICAL, "Requested length is less than min erase group size\n");
		return 1;
	}

	/* Calculate erase groups based on the length in blocks */
	num_erase_grps = len / erase_unit_sz;

	/* Start address of the erase range */
	erase_start = blk_addr;

	/* Last address of the erase range */
	erase_end = blk_addr + ((num_erase_grps - 1) * erase_unit_sz);

	/* Boundary check for overlap */
	blk_end = blk_addr + len;

	if (erase_end > blk_end)
	{
		dprintf(CRITICAL, "The erase group overlaps the max requested for erase\n");
		erase_end -= erase_unit_sz;
	}

	/* Send CMD35 for erase group start */
	if (mmc_send_erase_grp_start(dev, erase_start))
	{
		dprintf(CRITICAL, "Failed to send erase grp start address\n");
		return 1;
	}

	/* Send CMD36 for erase group end */
	if (mmc_send_erase_grp_end(dev, erase_end))
	{
		dprintf(CRITICAL, "Failed to send erase grp end address\n");
		return 1;
	}

	/*
	 * As per emmc 4.5 spec section 7.4.27, calculate the erase timeout
	 * erase_timeout = 300ms * ERASE_TIMEOUT_MULT * num_erase_grps
	 */
	if (MMC_CARD_MMC(card))
		erase_timeout = (300 * 1000 * card->ext_csd[MMC_ERASE_TIMEOUT_MULT] * num_erase_grps);
	else
		erase_timeout = (300 * 1000 * num_erase_grps);

	/* Send CMD38 to perform erase */
	if (mmc_send_erase(dev, erase_timeout))
	{
		dprintf(CRITICAL, "Failed to erase the specified partition\n");
		return 1;
	}

	return 0;
}

/*
 * Function: mmc get wp status
 * Arg     : mmc device structure, block address and buffer for getting wp status
 * Return  : 0 on Success, 1 on Failure
 * Flow    : Get the WP group status by sending CMD31
 */
uint32_t mmc_get_wp_status(struct mmc_device *dev, uint32_t addr, uint8_t *wp_status)
{
	struct mmc_command cmd;

	memset((struct mmc_command *)&cmd, 0, sizeof(struct mmc_command));

	cmd.cmd_index = CMD31_SEND_WRITE_PROT_TYPE;
	cmd.argument = addr;
	cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
	cmd.resp_type = SDHCI_CMD_RESP_R1;
	cmd.trans_mode = SDHCI_MMC_READ;
	cmd.data_present = 0x1;
	cmd.data.data_ptr = wp_status;
	cmd.data.num_blocks = 0x1;
	cmd.data.blk_sz = 0x8;

	if (sdhci_send_command(&dev->host, &cmd))
	{
		dprintf(CRITICAL, "Failed to get status of write protect bits\n");
		return 1;
	}

	return 0;
}

/*
 * Function: mmc set/clear WP on user area
 * Arg     : mmc device structure, block address,len, & flag to set or clear
 * Return  : 0 on success, 1 on failure
 * Flow    : Function to set/clear power on write protect on user area
 */

uint32_t mmc_set_clr_power_on_wp_user(struct mmc_device *dev, uint32_t addr, uint64_t len, uint8_t set_clr)
{
	struct mmc_command cmd;
	struct mmc_card *card = &dev->card;
	uint32_t wp_grp_size;
	uint32_t status;
	uint32_t num_wp_grps;
	uint32_t ret;
	uint32_t retry = 0;
	uint32_t i;

	memset((struct mmc_command *)&cmd, 0, sizeof(struct mmc_command));

	/* Convert len into blocks */
	len = len / MMC_BLK_SZ;
	wp_grp_size = dev->card.wp_grp_size;

	/* Disable PERM WP */
	ret = mmc_switch_cmd(&dev->host, &dev->card, MMC_SET_BIT, MMC_USR_WP, MMC_US_PERM_WP_DIS);

	if (ret)
	{
		dprintf(CRITICAL, "Failed to Disable PERM WP\n");
		return ret;
	}

	/* Read the default values for user WP */
	ret = mmc_get_ext_csd(&dev->host, &dev->card);

	if (ret)
	{
		dprintf(CRITICAL, "Failed to read ext csd for the card\n");
		return ret;
	}

	/* Check if user power on WP is disabled or perm WP is enabled */
	if ((dev->card.ext_csd[MMC_USR_WP] & MMC_US_PWR_WP_DIS)
		|| (dev->card.ext_csd[MMC_USR_WP] & MMC_US_PERM_WP_EN))
	{
		dprintf(CRITICAL, "Power on protection is disabled, cannot be set\n");
		return 1;
	}

	if (len < wp_grp_size)
	{
		dprintf(CRITICAL, "Length is less than min WP size, WP was not set\n");
		return 1;
	}

	/* Set power on USER WP */
	ret = mmc_switch_cmd(&dev->host, &dev->card, MMC_SET_BIT, MMC_USR_WP, MMC_US_PWR_WP_EN);

	if (ret)
	{
		dprintf(CRITICAL, "Failed to set power on WP for user\n");
		return ret;
	}

	num_wp_grps = ROUNDUP(len, wp_grp_size) / wp_grp_size;

	if (set_clr)
		cmd.cmd_index = CMD28_SET_WRITE_PROTECT;
	else
		cmd.cmd_index = CMD29_CLEAR_WRITE_PROTECT;

	cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
	cmd.resp_type = SDHCI_CMD_RESP_R1B;

	for(i = 0; i < num_wp_grps; i++)
	{
		/*
		* Standard emmc cards use byte mode addressing
		* convert the block address to byte address before
		* sending the command
		*/
		if (card->type == MMC_TYPE_STD_MMC)
			cmd.argument = (addr + (i * wp_grp_size)) * card->block_size;
		else
			cmd.argument = addr + (i * wp_grp_size);

		if (sdhci_send_command(&dev->host, &cmd))
			return 1;

		/* CMD28/CMD29 On failure returns address out of range error */
		if (MMC_ADDR_OUT_OF_RANGE(cmd.resp[0]))
		{
			dprintf(CRITICAL, "Address for CMD28/29 is out of range\n");
			return 1;
		}

		/* Check the card status */
		do
		{
			if (mmc_get_card_status(&dev->host, &dev->card, &status))
			{
				dprintf(CRITICAL, "Failed to get card status afterapplying write protect\n");
				return 1;
			}

		/* Time out for WP command */
		retry++;
		udelay(1000);
		if (retry == MMC_MAX_CARD_STAT_RETRY)
		{
			dprintf(CRITICAL, "Card status timed out after sending write protect command\n");
			return 1;
		}
		} while (!(status & MMC_READY_FOR_DATA) || (MMC_CARD_STATUS(status) == MMC_PROG_STATE));

	}

	return 0;
}

/* Function to put the mmc card to sleep */
void mmc_put_card_to_sleep(struct mmc_device *dev)
{
	struct mmc_command cmd = {0};
	struct mmc_card *card = &dev->card;

	cmd.cmd_index = CMD7_SELECT_DESELECT_CARD;
	cmd.argument = 0x00000000;
	cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
	cmd.resp_type = SDHCI_CMD_RESP_NONE;

	/* send command */
	if(sdhci_send_command(&dev->host, &cmd))
	{
		dprintf(CRITICAL, "card deselect error: %s\n", __func__);
		return;
	}
	if(MMC_CARD_MMC(card)){
		/*CMD5 is reserved in SD card */
		cmd.cmd_index = CMD5_SLEEP_AWAKE;
		cmd.argument = (card->rca << MMC_CARD_RCA_BIT) | MMC_CARD_SLEEP;
		cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
		cmd.resp_type = SDHCI_CMD_RESP_R1B;

		/* send command */
		if(sdhci_send_command(&dev->host, &cmd))
			dprintf(CRITICAL, "card sleep error: %s\n", __func__);
	}
}

/*
 * Switch the partition access type to rpmb or default
 */
static uint32_t mmc_sdhci_switch_part(struct mmc_device *dev, uint32_t type)
{
	uint32_t part_access;
	uint32_t ret;

	/* Clear the partition access */
	part_access = dev->card.ext_csd[MMC_PARTITION_CONFIG] & ~PARTITION_ACCESS_MASK;
	part_access |= type;

	ret = mmc_switch_cmd(&dev->host, &dev->card, MMC_ACCESS_WRITE, MMC_PARTITION_CONFIG, part_access);

	if (ret)
	{
		dprintf(CRITICAL, "Failed to switch partition to type: %u\n", type);
		return 1;
	}

	dev->card.ext_csd[MMC_PARTITION_CONFIG] = part_access;
	return 0;
}

static uint32_t mmc_sdhci_set_blk_cnt(struct mmc_device *dev, uint32_t blk_cnt, uint32_t rel_write)
{
	struct mmc_command cmd = {0};

	cmd.cmd_index = CMD23_SET_BLOCK_COUNT;
	cmd.argument = blk_cnt & 0x0000ffff;
	cmd.argument |= rel_write;
	cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
	cmd.resp_type = SDHCI_CMD_RESP_R1;

	if (sdhci_send_command(&dev->host, &cmd))
	{
		dprintf(CRITICAL, "Set block count failed: %s\n", __func__);
		return 1;
	}

	return 0;
}

uint32_t mmc_sdhci_rpmb_send(struct mmc_device *dev, struct mmc_command *cmd)
{
	int i;
	uint32_t retry = 5;
	uint32_t status;
	uint32_t rel_write = 0;
	uint32_t ret = 1;

	ASSERT(cmd);

	/* 1. Set the partition type to rpmb */
	if (mmc_sdhci_switch_part(dev, PART_ACCESS_RPMB))
		return 1;

	for (i = 0; i < MAX_RPMB_CMDS; i++)
	{
		if (!cmd[i].cmd_index)
			break;

		if (cmd[i].write_flag == true)
			rel_write = BIT(31);
		else
			rel_write = 0;

		/* 2. Set the block count using cmd23 */
		if (mmc_sdhci_set_blk_cnt(dev, cmd[i].data.num_blocks, rel_write))
			goto err;

		/* 3. Send the command */
		if (sdhci_send_command(&dev->host, &cmd[i]))
			goto err;
		do
		{
			/* 4. Poll for card status to ensure rpmb operation completeness */
			if (mmc_get_card_status(&dev->host, &dev->card, &status))
			{
				dprintf(CRITICAL, "Failed to get card status after rpmb operations\n");
				goto err;
			}

			retry--;
			udelay(500);
			if (!retry)
			{
				dprintf(CRITICAL, "Card status check timed out after rpmb operations\n");
				goto err;
			}
		} while(!(status & MMC_READY_FOR_DATA) || (MMC_CARD_STATUS(status) == MMC_PROG_STATE));
	}

	/* If we reach here, that means success */
	ret = 0;

err:
	/* 5. Switch the partition back to default type */
	if (mmc_sdhci_switch_part(dev, PART_ACCESS_DEFAULT))
		ret = 1;

	return ret;
}
